module cl {
	export class HexagonTileNetEngine {
		public constructor(public tileWidth : number, public tileHeight : number, public xOffset : number, public yOffset : number) {
		}

		public tile2coord(tile : Tile) : Coord {
			return new Coord(
				this.xOffset + this.tileWidth * ( tile.y%2==0 ? 0.5+tile.x : tile.x),
				this.yOffset + this.tileHeight * 0.75 * tile.y
			);
		}

		public coord2tile(coord : Coord) : Tile {
			let yn = coord.y - this.yOffset + 0.5 * this.tileHeight;
			let xn = coord.x - this.xOffset;
			let y1 = yn % (0.75 * this.tileHeight);
			let yid = Math.floor(yn / (0.75 * this.tileHeight));
			let dual = yid%2==0;
			if (y1 >= 0.25 * this.tileHeight){
				return new Tile(
					Math.floor(( dual ? xn : xn + 0.5 * this.tileWidth ) / this.tileWidth),
					yid
				);
			}else{
				let x1 = xn % this.tileWidth;
				let xid = Math.floor(xn / this.tileWidth);
				let identifier = 2 * y1 * this.tileWidth;
				if (dual){
					if (identifier < (this.tileHeight * (0.5 * this.tileWidth - x1))){
						return new Tile(xid, yid-1);
					}else if (identifier < (this.tileHeight * (x1 - 0.5 * this.tileWidth))){
						return new Tile(xid+1, yid-1);
					}else{
						return new Tile(xid, yid);
					}
				}else{
					if (identifier > (this.tileHeight * x1)){
						return new Tile(xid, yid);
					}else if (identifier > (this.tileHeight * (this.tileWidth - x1))){
						return new Tile(xid+1, yid);
					}else{
						return new Tile(xid, yid-1);
					}
				}
			}
		}

		public neighborMoves(tile : Tile) : Tile[] {
			if (tile.y%2==0){
				return [ new Tile(-1, 0 ),
							new Tile(0,  1 ),
							new Tile(1,  1 ),
							new Tile(1,  0 ),
							new Tile(1,  -1),
							new Tile(0,  -1)];
			} else {
				return [ new Tile(-1, 0 ),
							new Tile(-1, 1 ),
							new Tile(0,  1 ),
							new Tile(1,  0 ),
							new Tile(0,  -1),
							new Tile(-1, -1)];
			}
		}

		public neighborTiles(tile : Tile) : Tile[] {
			let moves = this.neighborMoves(tile);
			let tiles : Tile[] = [];
			for (let move of moves) {
				tiles.push(new Tile(tile.x + move.x, tile.y + move.y));
			}
			return tiles;
		}

		public headingIndex(heading : number) : number {
			let heading_tmp = heading + 30;
			while (heading_tmp < 0)
				heading_tmp += 360;
			while (heading_tmp >= 360)
				heading_tmp -= 360;

			let headingIndex = 3 - Math.floor(heading_tmp / 60);
			while (headingIndex < 0)
				headingIndex += 6;

			return headingIndex;
		}

		/**
		 * (-180~180], clockwise is +, anticlockwise is -
		 */
		public twoCoordHeading(coordbase : Coord, coordheading : Coord) : number {
			if (coordbase.x == coordheading.x){
				return coordheading.y > coordbase.y ? 90 : -90;
			}else{
				return Math.atan2(coordheading.y - coordbase.y, coordheading.x - coordbase.x) / Math.PI * 180;
			}
		}

		/**
		 * (-180~180], clockwise is +, anticlockwise is -
		 */
		public twoTileHeading(tilebase : Tile, tileheading : Tile) : number {
			let coordbase = this.tile2coord(tilebase);
			let coordheading = this.tile2coord(tileheading);
			return this.twoCoordHeading(coordbase, coordheading);
		}
	}

	export class TileEngine extends HexagonTileNetEngine {
		public constructor(public xSize : number, public ySize : number, public tileWidth : number, public tileHeight : number, public spaceRate : number = 4.0) {
			super(tileWidth, tileHeight, tileWidth * (spaceRate + 0.5), tileHeight * (spaceRate * 0.75 + 0.5));

			this.tileCount = xSize * ySize;
			this.width = (xSize + 0.5 + 2 * spaceRate) * this.tileWidth;
			this.height = (ySize - 1 + 2 * spaceRate) * 0.75 * this.tileHeight + this.tileHeight;

			// 各瓦片的邻接瓦片列表缓存
			for (let idy = 0; idy < this.ySize; idy++) {
				for (let idx = 0; idx < this.xSize; idx++) {
					let tile = new Tile(idx, idy);
					let neighbor_rough = this.neighborTiles(tile);
					let neighbor : number[] = [];
					for (let tmp of neighbor_rough){
						if (this.tileInBox(tmp)){
							neighbor.push(this.tile2tid(tmp));
						}
					}
					this.neighborCache.push(neighbor);
				}
			}
		}

		public tid2tile(tid : number) : Tile {
			let yid = Math.floor(tid / this.xSize);
			return new Tile(tid - ( yid * this.xSize), yid );
		}

		public tile2tid(tile : Tile) : number {
			return tile.y * this.xSize + tile.x;
		}

		public tid2coord(tid : number) : Coord {
			return this.tile2coord(this.tid2tile(tid));
		}

		public coord2tid(coord : Coord) : number {
			return this.tile2tid(this.coord2tile(coord));
		}

		public tileInBox(tile : Tile) : boolean {
			return tile.x >= 0 && tile.y >= 0 && tile.x < this.xSize && tile.y < this.ySize;
		}

		public coordInBox(coord : Coord) : boolean {
			return this.tileInBox(this.coord2tile(coord));
		}

		public foreachNeighbors(tid : number, cb : (neighbor : number) => boolean) : void {
			for (let neighborTid of this.neighborCache[tid]) {
				if (cb(neighborTid)){
					break;
				}
			}
		}

		/**
		 * (-180~180], clockwise is +, anticlockwise is -
		 */
		public twoTidHeading(tidBase : number, tidheading : number) : number {
			let coordbase = this.tid2coord(tidBase);
			let coordheading = this.tid2coord(tidheading);
			return this.twoCoordHeading(coordbase, coordheading);
		}

		public tileCount : number;
		public neighborCache : number[][] = [];
		public width : number;
		public height : number;
	}

}